1   package org.apache.lucene.benchmark.byTask.utils;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one or more
5    * contributor license agreements.  See the NOTICE file distributed with
6    * this work for additional information regarding copyright ownership.
7    * The ASF licenses this file to You under the Apache License, Version 2.0
8    * (the "License"); you may not use this file except in compliance with
9    * the License.  You may obtain a copy of the License at
10   *
11   *     http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing, software
14   * distributed under the License is distributed on an "AS IS" BASIS,
15   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16   * See the License for the specific language governing permissions and
17   * limitations under the License.
18   */
19  
20  import java.io.BufferedReader;
21  import java.io.IOException;
22  import java.io.Reader;
23  import java.io.StringReader;
24  import java.util.ArrayList;
25  import java.util.Collections;
26  import java.util.HashMap;
27  import java.util.List;
28  import java.util.Properties;
29  import java.util.StringTokenizer;
30  
31  /**
32   * Perf run configuration properties.
33   * <p>
34   * Numeric property containing ":", e.g. "10:100:5" is interpreted
35   * as array of numeric values. It is extracted once, on first use, and
36   * maintain a round number to return the appropriate value.
37   * <p>
38   * The config property "work.dir" tells where is the root of
39   * docs data dirs and indexes dirs. It is set to either of: <ul>
40   * <li>value supplied for it in the alg file;</li>
41   * <li>otherwise, value of System property "benchmark.work.dir";</li>
42   * <li>otherwise, "work".</li>
43   * </ul>
44   */
45  public class Config {
46  
47    // For tests, if verbose is not turned on, don't print the props.
48    private static final String DEFAULT_PRINT_PROPS = System.getProperty("tests.verbose", "true");
49    private static final String NEW_LINE = System.getProperty("line.separator");
50  
51    private int roundNumber = 0;
52    private Properties props;
53    private HashMap<String, Object> valByRound = new HashMap<>();
54    private HashMap<String, String> colForValByRound = new HashMap<>();
55    private String algorithmText;
56  
57    /**
58     * Read both algorithm and config properties.
59     *
60     * @param algReader from where to read algorithm and config properties.
61     * @throws IOException If there is a low-level I/O error.
62     */
63    public Config(Reader algReader) throws IOException {
64      // read alg file to array of lines
65      ArrayList<String> lines = new ArrayList<>();
66      BufferedReader r = new BufferedReader(algReader);
67      int lastConfigLine = 0;
68      for (String line = r.readLine(); line != null; line = r.readLine()) {
69        lines.add(line);
70        if (line.indexOf('=') > 0) {
71          lastConfigLine = lines.size();
72        }
73      }
74      r.close();
75      // copy props lines to string
76      StringBuilder sb = new StringBuilder();
77      for (int i = 0; i < lastConfigLine; i++) {
78        sb.append(lines.get(i));
79        sb.append(NEW_LINE);
80      }
81      // read props from string
82      this.props = new Properties();
83      props.load(new StringReader(sb.toString()));
84  
85      // make sure work dir is set properly 
86      if (props.get("work.dir") == null) {
87        props.setProperty("work.dir", System.getProperty("benchmark.work.dir", "work"));
88      }
89  
90      if (Boolean.valueOf(props.getProperty("print.props", DEFAULT_PRINT_PROPS)).booleanValue()) {
91        printProps();
92      }
93  
94      // copy algorithm lines
95      sb = new StringBuilder();
96      for (int i = lastConfigLine; i < lines.size(); i++) {
97        sb.append(lines.get(i));
98        sb.append(NEW_LINE);
99      }
100     algorithmText = sb.toString();
101   }
102 
103   /**
104    * Create config without algorithm - useful for a programmatic perf test.
105    * @param props - configuration properties.
106    */
107   public Config (Properties props) {
108     this.props = props;
109     if (Boolean.valueOf(props.getProperty("print.props",DEFAULT_PRINT_PROPS)).booleanValue()) {
110       printProps();
111     }
112   }
113 
114   @SuppressWarnings({"unchecked", "rawtypes"})
115   private void printProps() {
116     System.out.println("------------> config properties:");
117     List<String> propKeys = new ArrayList(props.keySet());
118     Collections.sort(propKeys);
119     for (final String propName : propKeys) {
120       System.out.println(propName + " = " + props.getProperty(propName));
121     }
122     System.out.println("-------------------------------");
123   }
124 
125   /**
126    * Return a string property.
127    *
128    * @param name name of property.
129    * @param dflt default value.
130    * @return a string property.
131    */
132   public String get(String name, String dflt) {
133     String vals[] = (String[]) valByRound.get(name);
134     if (vals != null) {
135       return vals[roundNumber % vals.length];
136     }
137     // done if not by round
138     String sval = props.getProperty(name, dflt);
139     if (sval == null) {
140       return null;
141     }
142     if (sval.indexOf(":") < 0) {
143       return sval;
144     } else if (sval.indexOf(":\\") >= 0 || sval.indexOf(":/") >= 0) {
145       // this previously messed up absolute path names on Windows. Assuming
146       // there is no real value that starts with \ or /
147       return sval;
148     }
149     // first time this prop is extracted by round
150     int k = sval.indexOf(":");
151     String colName = sval.substring(0, k);
152     sval = sval.substring(k + 1);
153     colForValByRound.put(name, colName);
154     vals = propToStringArray(sval);
155     valByRound.put(name, vals);
156     return vals[roundNumber % vals.length];
157   }
158 
159   /**
160    * Set a property.
161    * Note: once a multiple values property is set, it can no longer be modified.
162    *
163    * @param name  name of property.
164    * @param value either single or multiple property value (multiple values are separated by ":")
165    */
166   public void set(String name, String value) throws Exception {
167     if (valByRound.get(name) != null) {
168       throw new Exception("Cannot modify a multi value property!");
169     }
170     props.setProperty(name, value);
171   }
172 
173   /**
174    * Return an int property.
175    * If the property contain ":", e.g. "10:100:5", it is interpreted
176    * as array of ints. It is extracted once, on first call
177    * to get() it, and a by-round-value is returned.
178    *
179    * @param name name of property
180    * @param dflt default value
181    * @return a int property.
182    */
183   public int get(String name, int dflt) {
184     // use value by round if already parsed
185     int vals[] = (int[]) valByRound.get(name);
186     if (vals != null) {
187       return vals[roundNumber % vals.length];
188     }
189     // done if not by round 
190     String sval = props.getProperty(name, "" + dflt);
191     if (sval.indexOf(":") < 0) {
192       return Integer.parseInt(sval);
193     }
194     // first time this prop is extracted by round
195     int k = sval.indexOf(":");
196     String colName = sval.substring(0, k);
197     sval = sval.substring(k + 1);
198     colForValByRound.put(name, colName);
199     vals = propToIntArray(sval);
200     valByRound.put(name, vals);
201     return vals[roundNumber % vals.length];
202   }
203 
204   /**
205    * Return a double property.
206    * If the property contain ":", e.g. "10:100:5", it is interpreted
207    * as array of doubles. It is extracted once, on first call
208    * to get() it, and a by-round-value is returned.
209    *
210    * @param name name of property
211    * @param dflt default value
212    * @return a double property.
213    */
214   public double get(String name, double dflt) {
215     // use value by round if already parsed
216     double vals[] = (double[]) valByRound.get(name);
217     if (vals != null) {
218       return vals[roundNumber % vals.length];
219     }
220     // done if not by round 
221     String sval = props.getProperty(name, "" + dflt);
222     if (sval.indexOf(":") < 0) {
223       return Double.parseDouble(sval);
224     }
225     // first time this prop is extracted by round
226     int k = sval.indexOf(":");
227     String colName = sval.substring(0, k);
228     sval = sval.substring(k + 1);
229     colForValByRound.put(name, colName);
230     vals = propToDoubleArray(sval);
231     valByRound.put(name, vals);
232     return vals[roundNumber % vals.length];
233   }
234 
235   /**
236    * Return a boolean property.
237    * If the property contain ":", e.g. "true.true.false", it is interpreted
238    * as array of booleans. It is extracted once, on first call
239    * to get() it, and a by-round-value is returned.
240    *
241    * @param name name of property
242    * @param dflt default value
243    * @return a int property.
244    */
245   public boolean get(String name, boolean dflt) {
246     // use value by round if already parsed
247     boolean vals[] = (boolean[]) valByRound.get(name);
248     if (vals != null) {
249       return vals[roundNumber % vals.length];
250     }
251     // done if not by round 
252     String sval = props.getProperty(name, "" + dflt);
253     if (sval.indexOf(":") < 0) {
254       return Boolean.valueOf(sval).booleanValue();
255     }
256     // first time this prop is extracted by round 
257     int k = sval.indexOf(":");
258     String colName = sval.substring(0, k);
259     sval = sval.substring(k + 1);
260     colForValByRound.put(name, colName);
261     vals = propToBooleanArray(sval);
262     valByRound.put(name, vals);
263     return vals[roundNumber % vals.length];
264   }
265 
266   /**
267    * Increment the round number, for config values that are extracted by round number.
268    *
269    * @return the new round number.
270    */
271   public int newRound() {
272     roundNumber++;
273 
274     StringBuilder sb = new StringBuilder("--> Round ").append(roundNumber - 1).append("-->").append(roundNumber);
275 
276     // log changes in values
277     if (valByRound.size() > 0) {
278       sb.append(": ");
279       for (final String name : valByRound.keySet()) {
280         Object a = valByRound.get(name);
281         if (a instanceof int[]) {
282           int ai[] = (int[]) a;
283           int n1 = (roundNumber - 1) % ai.length;
284           int n2 = roundNumber % ai.length;
285           sb.append("  ").append(name).append(":").append(ai[n1]).append("-->").append(ai[n2]);
286         } else if (a instanceof double[]) {
287           double ad[] = (double[]) a;
288           int n1 = (roundNumber - 1) % ad.length;
289           int n2 = roundNumber % ad.length;
290           sb.append("  ").append(name).append(":").append(ad[n1]).append("-->").append(ad[n2]);
291         } else if (a instanceof String[]) {
292           String ad[] = (String[]) a;
293           int n1 = (roundNumber - 1) % ad.length;
294           int n2 = roundNumber % ad.length;
295           sb.append("  ").append(name).append(":").append(ad[n1]).append("-->").append(ad[n2]);
296         } else {
297           boolean ab[] = (boolean[]) a;
298           int n1 = (roundNumber - 1) % ab.length;
299           int n2 = roundNumber % ab.length;
300           sb.append("  ").append(name).append(":").append(ab[n1]).append("-->").append(ab[n2]);
301         }
302       }
303     }
304 
305     System.out.println();
306     System.out.println(sb.toString());
307     System.out.println();
308 
309     return roundNumber;
310   }
311 
312   private String[] propToStringArray(String s) {
313     if (s.indexOf(":") < 0) {
314       return new String[]{s};
315     }
316 
317     ArrayList<String> a = new ArrayList<>();
318     StringTokenizer st = new StringTokenizer(s, ":");
319     while (st.hasMoreTokens()) {
320       String t = st.nextToken();
321       a.add(t);
322     }
323     return a.toArray(new String[a.size()]);
324   }
325 
326   // extract properties to array, e.g. for "10:100:5" return int[]{10,100,5}. 
327   private int[] propToIntArray(String s) {
328     if (s.indexOf(":") < 0) {
329       return new int[]{Integer.parseInt(s)};
330     }
331 
332     ArrayList<Integer> a = new ArrayList<>();
333     StringTokenizer st = new StringTokenizer(s, ":");
334     while (st.hasMoreTokens()) {
335       String t = st.nextToken();
336       a.add(Integer.valueOf(t));
337     }
338     int res[] = new int[a.size()];
339     for (int i = 0; i < a.size(); i++) {
340       res[i] = a.get(i).intValue();
341     }
342     return res;
343   }
344 
345   // extract properties to array, e.g. for "10.7:100.4:-2.3" return int[]{10.7,100.4,-2.3}. 
346   private double[] propToDoubleArray(String s) {
347     if (s.indexOf(":") < 0) {
348       return new double[]{Double.parseDouble(s)};
349     }
350 
351     ArrayList<Double> a = new ArrayList<>();
352     StringTokenizer st = new StringTokenizer(s, ":");
353     while (st.hasMoreTokens()) {
354       String t = st.nextToken();
355       a.add(Double.valueOf(t));
356     }
357     double res[] = new double[a.size()];
358     for (int i = 0; i < a.size(); i++) {
359       res[i] = a.get(i).doubleValue();
360     }
361     return res;
362   }
363 
364   // extract properties to array, e.g. for "true:true:false" return boolean[]{true,false,false}. 
365   private boolean[] propToBooleanArray(String s) {
366     if (s.indexOf(":") < 0) {
367       return new boolean[]{Boolean.valueOf(s).booleanValue()};
368     }
369 
370     ArrayList<Boolean> a = new ArrayList<>();
371     StringTokenizer st = new StringTokenizer(s, ":");
372     while (st.hasMoreTokens()) {
373       String t = st.nextToken();
374       a.add(new Boolean(t));
375     }
376     boolean res[] = new boolean[a.size()];
377     for (int i = 0; i < a.size(); i++) {
378       res[i] = a.get(i).booleanValue();
379     }
380     return res;
381   }
382 
383   /**
384    * @return names of params set by round, for reports title
385    */
386   public String getColsNamesForValsByRound() {
387     if (colForValByRound.size() == 0) {
388       return "";
389     }
390     StringBuilder sb = new StringBuilder();
391     for (final String name : colForValByRound.keySet()) {
392       String colName = colForValByRound.get(name);
393       sb.append(" ").append(colName);
394     }
395     return sb.toString();
396   }
397 
398   /**
399    * @return values of params set by round, for reports lines.
400    */
401   public String getColsValuesForValsByRound(int roundNum) {
402     if (colForValByRound.size() == 0) {
403       return "";
404     }
405     StringBuilder sb = new StringBuilder();
406     for (final String name : colForValByRound.keySet()) {
407       String colName = colForValByRound.get(name);
408       String template = " " + colName;
409       if (roundNum < 0) {
410         // just append blanks
411         sb.append(Format.formatPaddLeft("-", template));
412       } else {
413         // append actual values, for that round
414         Object a = valByRound.get(name);
415         if (a instanceof int[]) {
416           int ai[] = (int[]) a;
417           int n = roundNum % ai.length;
418           sb.append(Format.format(ai[n], template));
419         } else if (a instanceof double[]) {
420           double ad[] = (double[]) a;
421           int n = roundNum % ad.length;
422           sb.append(Format.format(2, ad[n], template));
423         } else if (a instanceof String[]) {
424           String ad[] = (String[]) a;
425           int n = roundNum % ad.length;
426           sb.append(ad[n]);
427         } else {
428           boolean ab[] = (boolean[]) a;
429           int n = roundNum % ab.length;
430           sb.append(Format.formatPaddLeft("" + ab[n], template));
431         }
432       }
433     }
434     return sb.toString();
435   }
436 
437   /**
438    * @return the round number.
439    */
440   public int getRoundNumber() {
441     return roundNumber;
442   }
443 
444   /**
445    * @return Returns the algorithmText.
446    */
447   public String getAlgorithmText() {
448     return algorithmText;
449   }
450 
451 }